package org.tangerinejunction.imstats.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.AttributedString;

import javax.swing.AbstractAction;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableModel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
//import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.PieSectionLabelGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.util.SortOrder;
import org.tangerinejunction.imstats.data.Account;
import org.tangerinejunction.imstats.data.Entity;
import org.tangerinejunction.imstats.data.IMDataset;
import org.tangerinejunction.imstats.data.TableFactory;
import org.tangerinejunction.imstats.util.Constants;
import org.tangerinejunction.imstats.util.TableSorter;

import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.util.TableIterator;

/**
 * This is part of the new UI.  The SummaryFrame shows a summary
 * of statistics pertaining to the selected time range.  Right
 * now it's a pie chart.
 * 
 * @author meganthorsen
 */
public class SummaryFrame extends JFrame implements ActionListener, ComponentListener, MouseListener
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -7584028864804189593L;
	
	IMDataset dataset;
	Table dataTable;
	JTable table;
 	JPopupMenu tableContextMenu;
	ChartPanel chartPanel;
	JComboBox fieldSelector;
	Preferences preferences;
	
	/**
	 * 
	 * @param dataset	the dataset to analyze
	 * @param dataTable	the data table to analyze
	 */
	public SummaryFrame(IMDataset dataset, Table dataTable, Preferences preferences)
	{
		this.dataset = dataset;
		this.dataTable = dataTable;
		this.preferences = preferences;
		addComponentListener(this);
		
		createComponents();
		layoutComponents();
		setupEvents();
		setPreferredSize(new Dimension(300, 375));
		setTitle("Summary");
	}
	
	private void createComponents()
	{
		createTable();
		
		Object[] values = {
				TableFactory.FILE_SIZE_COL_NAME, 
				TableFactory.FILE_COUNT_COL_NAME, 
				TableFactory.FILE_DENSITY_COL_NAME };
		fieldSelector = new JComboBox(values);
		fieldSelector.setSelectedItem(TableFactory.FILE_SIZE_COL_NAME);
		
		chartPanel = new ChartPanel(createChart());
 		
 		tableContextMenu = new JPopupMenu();
 		JMenuItem hideItem = new JMenuItem("Hide selection in chart");
 		hideItem.setAction(new AbstractAction("Hide selection in chart")
 		{
			public void actionPerformed(ActionEvent e) 
			{
				int[] selection = table.getSelectedRows();
				for (int row : selection)
				{
					TableSorter sorter = (TableSorter)table.getModel();
					Tuple t = ((DataTableModel)sorter.getTableModel()).getTuple(sorter.modelIndex(row));
					t.set(Account.FILTERED, true);
				}
				((DataTableModel)((TableSorter)table.getModel()).getTableModel()).fireTableDataChanged();
			}
 		});
 		JMenuItem showItem = new JMenuItem("Show selection in chart");
 		showItem.setAction(new AbstractAction("Show selection in chart")
 		{
			public void actionPerformed(ActionEvent e) 
			{
				int[] selection = table.getSelectedRows();
				for (int row : selection)
				{
					TableSorter sorter = (TableSorter)table.getModel();
					Tuple t = ((DataTableModel)sorter.getTableModel()).getTuple(sorter.modelIndex(row));
					t.set(Account.FILTERED, false);
				}
				((DataTableModel)((TableSorter)table.getModel()).getTableModel()).fireTableDataChanged();
			}
 		});
 		tableContextMenu.add(hideItem);
 		tableContextMenu.add(showItem);
 		tableContextMenu.pack();
	}
	
	private void createTable()
	{
		//addColumns();
		DataTableModel model = new DataTableModel(dataTable);
		table = new JTable(model);
		RankedTableSorter sorter = new RankedTableSorter(TableFactory.ROW_COL_NAME, model, table.getTableHeader());
		table.setModel(sorter);
		int fileSizeCol = getFileSizeColumn();
		if (fileSizeCol > 0)
			sorter.setSortingStatus(fileSizeCol, TableSorter.DESCENDING);
		
		//chartViewer = new ChartViewer("Summary charts", dataTable, Application.TABLE_WINDOW_WIDTH, 0);
 		
 		table.addMouseListener(this);
	}
	
	private int getFileSizeColumn()
	{
		for (int i = 0; i < table.getColumnCount(); i++)
		{
			if (table.getColumnName(i).equals(TableFactory.FILE_SIZE_COL_NAME))
				return i;
		}
		return -1;
	}
	
	private JFreeChart createChart()
	{
		PieDataset dataset = createPieDataset();
		JFreeChart chart = ChartFactory.createPieChart("Summary", dataset, true, true, false);
		PiePlot plot = (PiePlot)chart.getPlot();
		plot.setShadowPaint(null);
		plot.setLegendLabelGenerator(new PieSectionLabelGenerator() 
		{
			public AttributedString generateAttributedSectionLabel(
					PieDataset dataset, Comparable key) {
				// TODO Auto-generated method stub
				return null;
			}

			public String generateSectionLabel(PieDataset dataset, Comparable key) {
				String label = key.toString();
				if (preferences.getLabelOption() == Preferences.LABEL_ONE_CHAR) {
					return label.substring(0, 1);
				} else if (preferences.getLabelOption() == Preferences.LABEL_SHORT_NAME) {
					return label.substring(0, Math.min(preferences.getNumLabelChars()-1, label.length()));
				} else {
					return label;
				}
				/* TODO: add the ordinality option */
			}
		});
		
		plot.setLabelGenerator(null);
		for (int i = 0; i < dataset.getItemCount(); i++)
			plot.setSectionPaint(dataset.getKey(i), Constants.colorPalette[i % 12]);
        plot.setBackgroundPaint(Constants.chartBackground);
		return chart;
	}
	
	private PieDataset createPieDataset()
	{
		int keyColIndex = -1;
		for (int i = 0; i < dataTable.getColumnCount(); i++)
		{
			if (dataTable.getColumnName(i).equals(Account.NAME_KEY) ||
					dataTable.getColumnName(i).equals(Entity.NAME_KEY))
			{
				keyColIndex = i;
				break;
			}
		}
		int valueColIndex = -1;
		String valueColName = (String)fieldSelector.getSelectedItem();
		for (int i = 0; i < dataTable.getColumnCount(); i++)
		{
			if (dataTable.getColumnName(i).equals(valueColName))
			{
				valueColIndex = i;
				break;
			}
		}
		if (valueColIndex == -1)
		{
			System.err.println("value col name: " + valueColName);
			for (int i = 0; i < dataTable.getColumnCount(); i++)
				System.err.println("col " + i + ": " + dataTable.getColumnName(i));
		}
		DefaultPieDataset pieDataset = new DefaultPieDataset();
		TableIterator rowIter = dataTable.iterator();
		int[] rows = new int[dataTable.getRowCount()];
		int index = 0;
		while (rowIter.hasNext())
		{
			rows[index] = rowIter.nextInt();
			index++;
		}
		for (int i = 0; i < rows.length; i++)
		{
			int row = rows[i];
			Comparable key = (Comparable)dataTable.get(row, keyColIndex);
			Number value = (Number)dataTable.get(row, valueColIndex);
			//if (value.doubleValue() > 0.0)
			//	pieDataset.setValue(key, value);
			Tuple t = dataTable.getTuple(row);
			Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
			if (value.doubleValue() > 0.0 && !isFiltered)
				pieDataset.setValue(key, value);
		}
		pieDataset.sortByValues(SortOrder.DESCENDING);
		return pieDataset; 
	}
	
	private void layoutComponents()
	{
		JSplitPane content = new JSplitPane();
		setContentPane(content);
		
		//GridBagLayout layout = new GridBagLayout();
		//setLayout(layout);
		//GridBagConstraints constraints = layout.getConstraints(this);		
		//constraints.gridx = 0;
		//constraints.gridy = 0;
		JPanel chartControlPanel = new JPanel();
		JLabel panelTitle = new JLabel("Summary");
		panelTitle.setFont(panelTitle.getFont().deriveFont(Font.BOLD));
		chartControlPanel.add(panelTitle);
		chartControlPanel.add(fieldSelector);
		
		/*
		add(chartControlPanel, constraints);
		constraints.gridy++;
		constraints.weightx = 1;
		constraints.weighty = 1;
		constraints.fill = GridBagConstraints.BOTH;
		add(chartPanel, constraints);
		*/
 		
 		JPanel upperPanel = new JPanel(new BorderLayout());
 		upperPanel.add(chartControlPanel, BorderLayout.NORTH);
 		upperPanel.add(chartPanel, BorderLayout.CENTER);
 		JPanel lowerPanel = new JPanel(new BorderLayout());
 		JScrollPane tablePane = new JScrollPane(table);
 		tablePane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
 		lowerPanel.add(tablePane, BorderLayout.CENTER);
 		content.setTopComponent(upperPanel);
 		content.setBottomComponent(lowerPanel);
 		content.setOrientation(JSplitPane.VERTICAL_SPLIT);
 		content.setOneTouchExpandable(true);
 		content.setDividerLocation(1d);
	}
	
	private void setupEvents()
	{
		fieldSelector.addActionListener(this);
	}
	
	/**
	 * Update the data table and chart.
	 * 
	 * @param dataTable	the data table
	 */
	public void updateDataTable(Table dataTable)
	{
		this.dataTable = dataTable;
		RankedTableSorter sorter = (RankedTableSorter)table.getModel();
		((DataTableModel)sorter.getTableModel()).data = dataTable;
		updateChart();
		updateTable();
	}
	
	/**
	 * Update the data table.
	 */
	public void updateTable()
	{
		//addColumns();
		RankedTableSorter sorter = (RankedTableSorter)table.getModel();
		sorter.fireTableDataChanged();
		sorter.fireTableStructureChanged();
		for (int i = 0; i < sorter.getColumnCount(); i++)
			if (sorter.getSortingStatus(i) != TableSorter.NOT_SORTED)
				sorter.setSortingStatus(i, sorter.getSortingStatus(i));
		table.updateUI();
		
		//chartViewer.updateDataTable(dataTable);
	}

	/**
	 * Update the chart.
	 */
	public void updateChart()
	{
		chartPanel.setChart(createChart());
		chartPanel.repaint();
	}

	public void actionPerformed(ActionEvent e) 
	{
		if (e.getSource() == fieldSelector)
			updateChart();
	}
	
	/**
	 * The model for the data table display.
	 * 
	 * @author meganthorsen
	 */
	protected class DataTableModel extends AbstractTableModel
	{
		private static final long serialVersionUID = 132940759456976493L;

 		private final String[] keepEntityColumns = 
 		{
 			TableFactory.ROW_COL_NAME,
 			Entity.FILTERED,
 			Entity.NAME_KEY,
 			Entity.ACCOUNTS_KEY,
 			TableFactory.FILE_COUNT_COL_NAME,
 			TableFactory.NORM_FILE_COUNT_COL_NAME,
 			TableFactory.FILE_SIZE_COL_NAME,
 			TableFactory.NORM_FILE_SIZE_COL_NAME,
 			TableFactory.FILE_DENSITY_COL_NAME,
 			TableFactory.NORM_FILE_DENSITY_COL_NAME//,
 			//TableFactory.FREQUENT_LENGTH_COL_NAME
 		};
 		
 		private final String[] keepAccountColumns = 
 		{
 			TableFactory.ROW_COL_NAME,
 			Account.PROTOCOL_KEY,
 			Account.MY_ACCOUNT_NAME_KEY,
 			Account.NAME_KEY,
 			Account.ENTITY_KEY,
 			//Account.LOG_FILES_KEY,
 			TableFactory.FILE_COUNT_COL_NAME,
 			TableFactory.NORM_FILE_COUNT_COL_NAME,
 			TableFactory.FILE_SIZE_COL_NAME,
 			TableFactory.NORM_FILE_SIZE_COL_NAME,
 			TableFactory.FILE_DENSITY_COL_NAME,
 			TableFactory.NORM_FILE_DENSITY_COL_NAME//,
 			//TableFactory.FREQUENT_LENGTH_COL_NAME
 		};
 		
		Table data;
		
		public DataTableModel(Table data)
		{
			this.data = data;
		}
		
		public int getColumnCount() 
		{
			if (data == dataset.getEntityTable())
				return keepEntityColumns.length;
			else
				return keepAccountColumns.length;
			/*
			if (data.getColumnNumber(Account.LOG_FILES_KEY) < 0)
				return data.getColumnCount();
			return data.getColumnCount()-1;
			*/
		}

		public int getRowCount() 
		{
			return data.getRowCount();
		}
		
		private int getDataColumn(int col)
		{
			String[] keepColumns = data == dataset.getEntityTable() 
					? keepEntityColumns 
					: keepAccountColumns;
 			int tableCol = 0;
 			int index = 0;
 			while (index <= col && tableCol < data.getColumnCount())
 			{
 				final String colName = data.getColumnName(tableCol);
 				int keepIndex = -1;
 	 			for (int i = 0; i < keepColumns.length; i++)
 	 			{
 	 				if (keepColumns[i].equals(colName))
 	 					keepIndex = i;
 	 			}
 				if (keepIndex >= 0)
 					index++;
 				tableCol++;
 			}
 			return tableCol-1;
 			/*
			if (col < data.getColumnNumber(Account.LOG_FILES_KEY) ||
					!data.canGet(Account.LOG_FILES_KEY, ArrayList.class))
				return col;
			else
				return col+1;
			*/
		}

		public Object getValueAt(int row, int col) 
		{
			int column = getDataColumn(col);
			if (data.getColumnName(column).equals(Account.ENTITY_KEY))
				return ((Entity)data.get(row, column)).getName();
			else if (data.getColumnName(column).equals(Entity.ACCOUNTS_KEY))
			{
				if (SummaryFrame.this.dataTable.getTuple(row) instanceof Entity) 
				{
					Entity entity = (Entity)SummaryFrame.this.dataTable.getTuple(row);
					return entity.getAccounts().size();
				}
				else 
					return 1;
			}
			//if (data.getColumnName(col).equals(Account.LOG_FILES_KEY))
			//	return ((ArrayList)data.get(row, col)).size();
 			if (data.getColumnName(getDataColumn(col)).equals(Account.FILTERED))
 				return (Boolean)getTuple(row).get(Account.FILTERED);
			return data.get(row, column);
		}
 		
 		public Tuple getTuple(int row)
 		{
 			return data.getTuple(row);
 		}
		
		public String getColumnName(int col)
		{
			return data.getColumnName(getDataColumn(col));
		}
		
		public Class<?> getColumnClass(int col)
		{
			int column = getDataColumn(col);
			if (data.getColumnName(column).equals(Account.ENTITY_KEY))
				return String.class;
			if (data.getColumnName(column).equals(Entity.ACCOUNTS_KEY))
				return Integer.class;
			//if (data.getColumnName(col).equals(Account.LOG_FILES_KEY))
			//	return Integer.class;
			return data.getColumnType(column);
		}
		
		public boolean isCellEditable(int row, int col)
		{
			if (data.getColumnName(getDataColumn(col)).equals(Account.ENTITY_KEY))
				return true;
 			else if (data.getColumnName(getDataColumn(col)).equals(Account.FILTERED))
 				return true;
			else
				return false;
		}

		public void setValueAt(Object value, int row, int col)
		{
			if (data.getColumnName(getDataColumn(col)).equals(Account.ENTITY_KEY))
			{
				Account account = (Account)data.getTuple(row);
				dataset.addAccountToEntityByName((String)value, account);
			}
 			else if (data.getColumnName(getDataColumn(col)).equals(Account.FILTERED))
 			{
 				Tuple t = getTuple(row);
 				t.set(Account.FILTERED, value);
 			}
		}
	}
	
	protected class RankedTableSorter extends TableSorter
	{
		private static final long serialVersionUID = 4963425141701216502L;
		
		private String rankColName;
		
		public RankedTableSorter(String rankColName)
		{
			super();
			this.rankColName = rankColName;
		}
		
		public RankedTableSorter(String rankColName, TableModel model)
		{
			super(model);
			this.rankColName = rankColName;
		}
		
		public RankedTableSorter(String rankColName, TableModel model, JTableHeader header)
		{
			super(model, header);
			this.rankColName = rankColName;
		}

	    public Object getValueAt(int row, int column) 
	    {
	    	if (tableModel.getColumnName(column).equals(rankColName))
	    		return row+1;
	        return tableModel.getValueAt(modelIndex(row), column);
	    }
	}

	public void componentHidden(ComponentEvent e) {}

	public void componentMoved(ComponentEvent e) {}

	public void componentResized(ComponentEvent e)
	{
		((JSplitPane)getContentPane()).setDividerLocation(1d);
	}

	public void componentShown(ComponentEvent e) {}

	public void mouseClicked(MouseEvent e) {}

	public void mouseEntered(MouseEvent e) {}

	public void mouseExited(MouseEvent e) {}

	public void mousePressed(MouseEvent e) 
	{
		tableContextMenu.setVisible(false);
		if (e.isPopupTrigger())
		{
			JSplitPane pane = (JSplitPane)getContentPane();
			tableContextMenu.show(this, e.getX(), e.getY() + pane.getDividerLocation() + tableContextMenu.getHeight());
		}
	}

	public void mouseReleased(MouseEvent e) {}
}
